﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace Server
{
    class Program
    {
        //define opCode;
        const int LOGIN = 0;
        const int REG = 1;
        const int EXIT = 2;
        const int LOGINSUCCESSFULL = 3;
        const int UPDATECLIENTLIST = 4;
        const int SHARE = 5;
        const int SEARCH = 6;
        const int DOWNLOAD = 7;
        const int LOGOUT = 8;
        const int LOGINFAIL = 9;
        const int REQUEST_DOWNLOAD = 10;
        const int TRACK_AVAILABLE = 11;
        const int TRACK_NOTAVAILABLE = 12;
        const int READY_DOWNLOAD = 13;
        const int REG_SUCCESSFUL = 14;
        const int REG_FAILURE = 15;
        //define Status
        //enum Status { CONNECTED, LOGGEDIN, FREE, BUSY };

        private TcpListener tcpListener;
        private List<ClientInfo> clientList = new List<ClientInfo>();

        private static Mutex mutexUpdateClientList = new Mutex();

        

        public Program(int port)
        {
            tcpListener = new TcpListener(IPAddress.Any, port);
            tcpListener.Start();
            Console.WriteLine("Server starting...");
            DataProvider.ConnectionString = @"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\Users\Toshiba\Desktop\Database.accdb";

        }

        public void Start()
        {
            while (true)
            {
                TcpClient tcpClient = tcpListener.AcceptTcpClient();
                Console.WriteLine("A client have connected");
                Thread thread = new Thread(new ParameterizedThreadStart(ProcessRotine));
                ClientInfo clientInfo = new ClientInfo();
                clientInfo.Socket = tcpClient;
                clientInfo.ID = clientList.Count;
                clientInfo.Status = Status.CONNECTED;
                clientInfo.Name = "";
                clientList.Add(clientInfo);
                object[] para = new object[] { clientInfo };
                thread.Start(para);
            }
        }

        public int getIndex(TcpClient tcpClient)
        {
            for (int i = 0; i < clientList.Count; i++)
            {
                if (tcpClient == clientList[i].Socket)
                    return i;
            }
            return -1;
        }


        public List<int> searchTrack(string Name, int type)
        {
            List<int> result = new List<int>();
            for (int i = 0; i < clientList.Count; i++)
            {
                if (clientList[i].Status == Status.LOGGEDIN)
                {
                    for (int j = 0; j < clientList[i].TrackList.Count; j++)
                    {
                        /*searchType:
                         * 0    Title
                         * 1    Artist
                         * 2    Composer
                         * 3    Album
                         */
                        switch (type)
                        {
                            case 0:
                                {
                                    if (clientList[i].TrackList[j].Title.ToUpper() == Name.ToUpper())
                                        result.Add(i);
                                }
                                break;
                            case 1:
                                if (clientList[i].TrackList[j].Artist.ToUpper() == Name.ToUpper())
                                    result.Add(i);
                                break;
                            case 2:
                                if (clientList[i].TrackList[j].Composer.ToUpper() == Name.ToUpper())
                                    result.Add(i);
                                break;
                            case 3:
                                if (clientList[i].TrackList[j].Album.ToUpper() == Name.ToUpper())
                                    result.Add(i);
                                break;
                            default:
                                break;
                        }
                    }

                }
            }

            return result;
        }

        private TcpClient getSocket(string name)
        {
            for(int i=0; i<clientList.Count; i++)
            {
                if(clientList[i].Name == name)
                {
                    return clientList[i].Socket;
                }
            }
            return null;
        }

        private string getLocation(string clientName, string trackName){
            for(int i=0; i<clientList.Count; i++)
            {
                if(clientList[i].Name.ToUpper() == clientName.ToUpper())
                {
                    for(int j=0; j<clientList[i].TrackList.Count; i++)
                    {
                        if(clientList[i].TrackList[j].Title.ToUpper() == trackName.ToUpper())
                        {
                            return clientList[i].TrackList[j].Location;
                        }
                    }
                }
            }
            return null;
        }

        //private TcpClient getClient(String IP)
        //{
        //    for (int i = 0; i < clientList.Count; i++)
        //    {
        //        string IPi = ((IPEndPoint)clientList[i].Socket.Client.RemoteEndPoint).Address.ToString();
        //        if (IP == IPi)
        //        {
        //            return clientList[i].Socket;
        //        }
        //    }
        //    return null;
        //}

        private string getClientName(TcpClient socket)
        {
            for(int i=0; i<clientList.Count; i++)
            {
                if(clientList[i].Socket == socket)
                {
                    return clientList[i].Name;
                }
            }
            return null;
        }

        public void ProcessRotine(object para)
        {
            object[] objectPara = (object[])para;
            ClientInfo clientInfo = (ClientInfo)objectPara[0];
            NetworkStream netStream = clientInfo.Socket.GetStream();

            byte[] bufferOpcode = new byte[4];
            while (netStream.Read(bufferOpcode, 0, 4) != 0)//receive opcode
            {
                int opCode = BitConverter.ToInt32(bufferOpcode, 0);
                switch (opCode)
                {
                    case REQUEST_DOWNLOAD:
                        {
                            byte[] numberBuffer = new byte[4];
                            netStream.Read(numberBuffer, 0, 4);//read length of client B name
                            int len = BitConverter.ToInt32(numberBuffer, 0);
                            byte[] buffer = new byte[len];
                            netStream.Read(buffer, 0, len);//read client B name
                            string clientBName = Encoding.ASCII.GetString(buffer);
                            netStream.Read(numberBuffer, 0, 4);//read length track
                            len = BitConverter.ToInt32(numberBuffer, 0);
                            buffer = new byte[len];
                            netStream.Read(buffer, 0, len);//read track name
                            string trackName = Encoding.ASCII.GetString(buffer);

                            NetworkStream nsB = getSocket(clientBName).GetStream();
                            nsB.Write(BitConverter.GetBytes(REQUEST_DOWNLOAD), 0, 4);//send opcode

                            string location = getLocation(clientBName, trackName);
                            nsB.Write(BitConverter.GetBytes(location.Length), 0, 4);//send length of location to B
                            nsB.Write(Encoding.ASCII.GetBytes(location), 0, location.Length);//send location to B
                            string AName = getClientName(clientInfo.Socket);
                            nsB.Write(BitConverter.GetBytes(AName.Length), 0, 4);//send length of client A (A wants to download from B)
                            nsB.Write(Encoding.ASCII.GetBytes(AName), 0, AName.Length);//send length
                            
                        }
                        break;
                    case TRACK_AVAILABLE:
                        {
                            byte[] numberBuffer = new byte[4];
                            netStream.Read(numberBuffer, 0, 4);//read port
                            int port = BitConverter.ToInt32(numberBuffer, 0);
                            netStream.Read(numberBuffer, 0, 4);//read length of client A name
                            byte[] buffer = new byte[BitConverter.ToInt32(numberBuffer, 0)];
                            netStream.Read(buffer, 0, buffer.Length);//read client A name
                            string clientAName = Encoding.ASCII.GetString(buffer);
                            NetworkStream nsA = getSocket(clientAName).GetStream();
                            nsA.Write(BitConverter.GetBytes(READY_DOWNLOAD), 0, 4);//send opcode to A
                            nsA.Write(BitConverter.GetBytes(port), 0, 4);//
                        }
                        break;
                    case LOGOUT:
                        {
                            int index = getIndex(clientInfo.Socket);
                            clientList[index].Status = Status.CONNECTED;
                            clientList[index].TrackList.Clear();
                            updateClientList();
                        }
                        break;
                    case SEARCH:////////////////////////////////////////////////////////////////////////////
                        {

                            byte[] numberBuffer = new byte[4];
                            netStream.Read(numberBuffer, 0, 4);//read length of search string
                            int len = BitConverter.ToInt32(numberBuffer, 0);
                            byte[] buffer = new byte[len];
                            netStream.Read(buffer, 0, len);//read search string
                            string searchString = Encoding.ASCII.GetString(buffer);
                            netStream.Read(numberBuffer, 0, 4);//read search type
                            int searchType = BitConverter.ToInt32(numberBuffer, 0);

                            //////////////////////////////////////////result////////////////////////////////////////////
                            List<int> result = searchTrack(searchString, searchType);
                            netStream.Write(BitConverter.GetBytes(SEARCH), 0, 4);//send opcode

                            netStream.Write(BitConverter.GetBytes(result.Count), 0, 4);//send number of client that have that track
                            ////////send Name and IP
                            for (int i = 0; i < result.Count; i++)
                            {
                                netStream.Write(BitConverter.GetBytes(clientList[result[i]].Name.Length), 0, 4);//send length of client Name
                                netStream.Write(Encoding.ASCII.GetBytes(clientList[result[i]].Name), 0, clientList[result[i]].Name.Length);//send client Name

                                string IP = ((IPEndPoint)clientList[result[i]].Socket.Client.RemoteEndPoint).Address.ToString();
                                netStream.Write(BitConverter.GetBytes(IP.Length), 0, 4);//send length of IP
                                netStream.Write(Encoding.ASCII.GetBytes(IP), 0, IP.Length);//send IP
                            }


                        }
                        break;
                    case SHARE:////////////////////////////////////////////////////////////////////////////
                        {
                            int numberOfTrack;
                            byte[] numberBuffer = new byte[4];
                            netStream.Read(numberBuffer, 0, 4);
                            numberOfTrack = BitConverter.ToInt32(numberBuffer, 0);
                            byte[] buffer;
                            int len;
                            for (int i = 0; i < numberOfTrack; i++)
                            {
                                TrackInfo trackInfo = new TrackInfo();

                                netStream.Read(numberBuffer, 0, 4); //read length of title
                                len = BitConverter.ToInt32(numberBuffer, 0);
                                buffer = new byte[len];
                                netStream.Read(buffer, 0, len);//read title
                                trackInfo.Title = Encoding.ASCII.GetString(buffer);

                                netStream.Read(numberBuffer, 0, 4); //read length of album
                                len = BitConverter.ToInt32(numberBuffer, 0);
                                buffer = new byte[len];
                                netStream.Read(buffer, 0, len);//read album
                                trackInfo.Album = Encoding.ASCII.GetString(buffer);

                                netStream.Read(numberBuffer, 0, 4); //read length of composer
                                len = BitConverter.ToInt32(numberBuffer, 0);
                                buffer = new byte[len];
                                netStream.Read(buffer, 0, len);//read composer
                                trackInfo.Composer = Encoding.ASCII.GetString(buffer);

                                netStream.Read(numberBuffer, 0, 4); //read length of artist
                                len = BitConverter.ToInt32(numberBuffer, 0);
                                buffer = new byte[len];
                                netStream.Read(buffer, 0, len);//read artist
                                trackInfo.Artist = Encoding.ASCII.GetString(buffer);

                                netStream.Read(numberBuffer, 0, 4); //read length of location
                                len = BitConverter.ToInt32(numberBuffer, 0);
                                buffer = new byte[len];
                                netStream.Read(buffer, 0, len);//read location
                                trackInfo.Location = Encoding.ASCII.GetString(buffer);

                                /////////////////////////////////////////////sync here//////////////
                                int index = getIndex(clientInfo.Socket);
                                clientList[index].TrackList.Add(trackInfo);

                            }
                        }
                        break;
                    case EXIT:////////////////////////////////////////////////////////////////////////////
                        {
                            int index = getIndex(clientInfo.Socket);
                            if (index >= 0)
                            {
                                clientList.RemoveAt(index);
                                for (int i = 0; i < clientList.Count; i++)
                                {
                                    NetworkStream nsToClient = clientList[i].Socket.GetStream();
                                    nsToClient.Write(BitConverter.GetBytes(UPDATECLIENTLIST), 0, 4);//send opcode
                                    nsToClient.Write(BitConverter.GetBytes(clientList.Count), 0, 4);//send number of clients
                                    for (int j = 0; j < clientList.Count; j++)
                                    {
                                        int len = clientList[j].Name.Length;
                                        nsToClient.Write(BitConverter.GetBytes(len), 0, 4);//send length of client Name
                                        byte[] buffer = Encoding.ASCII.GetBytes(clientList[j].Name);
                                        nsToClient.Write(buffer, 0, buffer.Length);//send client Name

                                        string IP = ((IPEndPoint)clientList[j].Socket.Client.RemoteEndPoint).Address.ToString();
                                        nsToClient.Write(BitConverter.GetBytes(IP.Length), 0, 4);//send length of client ip
                                        buffer = Encoding.ASCII.GetBytes(IP);
                                        nsToClient.Write(buffer, 0, buffer.Length);
                                    }
                                }
                            }
                            Console.WriteLine("A client have quitted");
                            return;
                        }
                    case REG:////////////////////////////////////////////////////////////////////////////
                        {
                            byte[] lenBuffer = new byte[4];
                            int len;
                            netStream.Read(lenBuffer, 0, 4);//receive length of userName
                            len = BitConverter.ToInt32(lenBuffer, 0);
                            byte[] bufferUserName = new byte[len];
                            netStream.Read(bufferUserName, 0, len);  //receive user Name:
                            string userName = Encoding.ASCII.GetString(bufferUserName, 0, len);

                            netStream.Read(lenBuffer, 0, 4);//receive length of pass
                            len = BitConverter.ToInt32(lenBuffer, 0);
                            byte[] bufferPass = new byte[len];
                            netStream.Read(bufferPass, 0, len);      //receive pass
                            string pass = Encoding.ASCII.GetString(bufferPass, 0, len);

                            netStream.Read(lenBuffer, 0, 4);//receive length of repass
                            len = BitConverter.ToInt32(lenBuffer, 0);
                            bufferPass = new byte[len];
                            netStream.Read(bufferPass, 0, len);      //receive repass
                            string repass = Encoding.ASCII.GetString(bufferPass);
                            //////////////XU LY LUU LAI ACCOUNT
                            AccountDAO accountDAO = new AccountDAO();
                            bool result = accountDAO.registerAccount(userName, pass);
                            if(result)
                            {
                                netStream.Write(BitConverter.GetBytes(REG_SUCCESSFUL), 0, 4);//send opcode reg successful
                            }else
                            {
                                netStream.Write(BitConverter.GetBytes(REG_FAILURE), 0, 4);//send opcode reg failed
                            }

                        }
                        break;
                    case LOGIN:////////////////////////////////////////////////////////////////////////////
                        {
                            byte[] lenBuffer = new byte[4];
                            int len;
                            netStream.Read(lenBuffer, 0, 4);//receive length of userName
                            len = BitConverter.ToInt32(lenBuffer, 0);
                            byte[] bufferUserName = new byte[len];
                            netStream.Read(bufferUserName, 0, len);  //receive user Name:
                            string userName = Encoding.ASCII.GetString(bufferUserName, 0, len);

                            netStream.Read(lenBuffer, 0, 4);//receive length of pass
                            len = BitConverter.ToInt32(lenBuffer, 0);
                            byte[] bufferPass = new byte[len];
                            netStream.Read(bufferPass, 0, len);      //receive pass
                            string pass = Encoding.ASCII.GetString(bufferPass, 0, len);

                            

                            if (TestAccount(userName, pass))
                            {
                                netStream.Write(BitConverter.GetBytes(LOGINSUCCESSFULL), 0, 4);
                                int index = getIndex(clientInfo.Socket);
                                if (index != -1)
                                {
                                    clientList[index].Name = userName;
                                    //change Status
                                    clientList[index].Status = Status.LOGGEDIN;
                                }
                            }else
                            {
                                netStream.Write(BitConverter.GetBytes(LOGINFAIL), 0, 4);
                            }
                            //change Status


                            //notify all clients
                            updateClientList();
                        }
                        break;
                    default:
                        break;
                }
            }
            Console.WriteLine("zzz...");
        }

        public bool TestAccount(string userName, string passWord)
        {
            AccountDAO accountDAO = new AccountDAO();
            return accountDAO.testAccount(userName, passWord);
        }


        private void updateClientList()
        {
            //////////////////////SYNC///////////////////////////////////////
            int len;
            //down mutexUpdateClientList
            int numberOfClient = 0;
            for (int i = 0; i < clientList.Count; i++)
            {
                if(clientList[i].Status == Status.LOGGEDIN)
                {
                    numberOfClient++;
                }
            }
            mutexUpdateClientList.WaitOne();
            for (int i = 0; i < clientList.Count; i++)
            {
                if (clientList[i].Status == Status.LOGGEDIN)
                {
                    NetworkStream nsToClient = clientList[i].Socket.GetStream();
                    nsToClient.Write(BitConverter.GetBytes(UPDATECLIENTLIST), 0, 4);//send opcode
                    nsToClient.Write(BitConverter.GetBytes(numberOfClient), 0, 4);//send number of clients
                    for (int j = 0; j < clientList.Count; j++)
                    {
                        if (clientList[j].Status == Status.LOGGEDIN)
                        {
                            len = clientList[j].Name.Length;
                            nsToClient.Write(BitConverter.GetBytes(len), 0, 4);//send length of client Name
                            byte[] buffer = Encoding.ASCII.GetBytes(clientList[j].Name);
                            nsToClient.Write(buffer, 0, buffer.Length);//send client Name

                            string IP = ((IPEndPoint)clientList[j].Socket.Client.RemoteEndPoint).Address.ToString();
                            nsToClient.Write(BitConverter.GetBytes(IP.Length), 0, 4);//send length of client ip
                            buffer = Encoding.ASCII.GetBytes(IP);
                            nsToClient.Write(buffer, 0, buffer.Length);
                        }
                    }
                }
            }
            //up mutex
            mutexUpdateClientList.ReleaseMutex();
        }

        static void Main(string[] args)
        {
            Program p = new Program(1234);
            p.Start();

        }
    }
}
